#include "Network.h"

#include "Game.h"
#include "Ship.h"
#include "NetworkEvents.h"

CMultiplayer::CMultiplayer(CGame* pGame)
{
    
    m_pGame = pGame;
    
    m_bIsServer = false;
    
    m_pServer = 0;
    m_pClient = 0;
    
}

void CMultiplayer::host(unsigned short iMaxPlayers, unsigned short iPort)
{
    
    m_bIsServer = true;
    
    m_pServer = RakNetworkFactory::GetRakServerInterface();
    m_pServer->Start(iMaxPlayers, 0, 30, iPort);
    m_pServer->StartOccasionalPing();
    
    //TODO: Consider allowing server acting as client by calling join to self here
    //join
    
}

void CMultiplayer::join(const char* cpHostIP, unsigned short iServerPort, unsigned short iClientPort)
{
    
    m_bIsServer = false;
    
    m_pClient = RakNetworkFactory::GetRakClientInterface();
    m_pClient->Connect(cpHostIP, iServerPort, iClientPort, 0, 30);

    //printf("Connecting...\n");
    
    //TODO: Add timeout checking
    
    while(!m_pClient->IsConnected()) {

        Sleep(100);

    }
    
    m_pClient->StartOccasionalPing();
    
    //Create our local CPlayer instance
    m_pPlayer = new CPlayer();
    
}

void CMultiplayer::checkForPackets()
{
    
    //TODO: Implement handling of the following Rak-Net built in packet types:
	//NOTE: These can all be implemented in level 1 code, as they are fundamental and static
	//ID_DISCONNECTION_NOTIFICATION 		[S\C]The system specified in Packet::playerID has disconnected from us. For the client, this would mean the server has shutdown.
	//ID_CONNECTION_LOST					[S\C]Reliable packets cannot be delivered to the system specifed in Packet::playerID. The connection to that system has been closed. (ungraceful disconnect)
	//ID_CONNECTION_ATTEMPT_FAILED			[S\C]Sent to the player when a connection request cannot be completed due to inability to connect.
	//ID_CONNECTION_REQUEST_ACCEPTED		[C]In a client/server environment, our connection request to the server has been accepted.
    //ID_REMOTE_DISCONNECTION_NOTIFICATION 	[C]In a client/server environment, a client other than ourselves has disconnected gracefully. Packet::playerID is modified to reflect the playerID of this client. 
	//ID_REMOTE_CONNECTION_LOST 			[C]In a client/server environment, a client other than ourselves has been forcefully dropped. Packet::playerID is modified to reflect the playerID of this client. 
	//ID_REMOTE_NEW_INCOMING_CONNECTION 	[C]In a client/server environment, a client other than ourselves has connected. Packet::playerID is modified to reflect the playerID of this client. 
	//ID_REMOTE_EXISTING_CONNECTION 		[C]On our initial connection to the server, we are told of every other client in the game. Packet::playerID is modified to reflect the playerID of this client.
    
    //std::cout << "Start of packet checking" << std::endl;
    if (m_bIsServer) {
        
        Packet* pPacket = m_pServer->Receive();
        if (pPacket) {
            
            RakNet::BitStream bsData(pPacket->data, pPacket->length, false);
            
            unsigned char cTypeID;
            unsigned long iTimeStamp;
            
            bsData.Read(cTypeID);
            
            //Check for a timestamp
            if (cTypeID == ID_TIMESTAMP) {
                bsData.Read(iTimeStamp);
                bsData.Read(cTypeID);
            }
            
            switch (cTypeID) {
                case ID_NEW_INCOMING_CONNECTION:
                    std::cout << "New incoming connection from player " << pPacket->playerId.ToString() << std::endl;
                    //NOTE: This is a core multiplayer event, so can justify specific handler function
                    onClientConnect(pPacket->playerId);
                    break;
                case ID_START_STOP:
                    {
                        
                        CPlayer* pPlayer = getPlayer(pPacket->playerId);
                        
                        if (0 == pPlayer) {
			            	std::cerr << "StartStopEvent Error: Could not find CPlayer instance with ID " << pPacket->playerId.ToString() << ". Event cancelled." << std::endl;
			            	return;
						}
                        
                        CStartStopEvent event(pPlayer);
                        event.respond(bsData);
                        
                    }
                	break;
                /*case ID_CHANGE_VEL:
                    {
                    
                    std::cout << "New change vel request" << std::endl;
                    
                    CChangeVelEvent event;
                    event.respond(bsData);
                    
                    }
                    
                    break;*/
                    
                default:
                    std::cerr << "Network Error: Unknown packet type " << (unsigned int)cTypeID << std::endl;
                    break;
            }
            
            //TODO: More packet type handling
            
        }
        
    }
    
    //std::cout << "Middle of packet checking" << std::endl;
    
    else {
        
        Packet* pPacket = m_pClient->Receive();
        if (pPacket) {
            
            RakNet::BitStream bsData(pPacket->data, pPacket->length, false);
            
            unsigned char cTypeID;
            unsigned long iTimeStamp;
            
            bsData.Read(cTypeID);
            
            //Check for a timestamp
            if (cTypeID == ID_TIMESTAMP) {
                bsData.Read(iTimeStamp);
                bsData.Read(cTypeID);
            }
            
            switch (cTypeID) {
                case ID_CONNECTION_REQUEST_ACCEPTED:
                    std::cout << "Connection accepted" << std::endl;
                    //We've received confirmation of our connection to the server, and have been
                    //given a PlayerID.
                    //TODO: Make sure this is doing what it's supposed to to (i.e., the id is right).
					//TODO: Possibly (likely) should be replaced with m_pClient->GetPlayerID()
					getPlayer()->setID(pPacket->playerId);
                    break;
                case ID_NETWORKOBJECT_UPDATE:
                    //std::cout << "Update object" << std::endl;
                    updateObjectFromBitStream(bsData);
                    
                    break;
                case ID_CREATE_NETWORKOBJECT:
                    {
                    CCreateObjectEvent event;
                    event.respond(bsData);
                    
                    std::cout << "Create object" << std::endl;
                    //createObjectFromBitStream(bsData);
                    }
                    break;
                case ID_CHANGE_VEL:
                    {
                        
                        CChangeVelEvent serverEvent;
                        serverEvent.respond(bsData);
                        
                    }
                    break;
                case ID_SPAWN:
					{
						
						CSpawnEvent serverEvent;
						serverEvent.respond(bsData);
						
					}
					break;
                default:
                    std::cerr << "Network Error: Unknown packet type " << (unsigned int)cTypeID << std::endl;
                    break;
            }
            
            //TODO: More packet type handling
            
        }
        
    }
    
}

void CMultiplayer::updateObjectFromBitStream(RakNet::BitStream& bsData)
{
    
    //Read the network ID of the object
    NetworkID nidObjectID;
    bsData.Read(nidObjectID);
    
    //Get a pointer to the object specified by nidObjectID
    CNetworkObject* pObject = (CNetworkObject*) NetworkIDGenerator::GET_OBJECT_FROM_ID(nidObjectID);
    
    if (!pObject) {
        
        std::cout << "Error - network object with networkID " << nidObjectID.localSystemId << " not found" << std::endl;
        return;
        
    }
    else {
        
        //std::cout << "UnError - network object with networkID " << nidObjectID.localSystemId << " found" << std::endl;
        
    }
    
    //Update the object using the bitstream
    pObject->updateFromBitStream(bsData);

}

//TODO: Think about how to do server acting as client
//      Possibly if (m_bIsServer) { //Do server stuff } //Do client stuff
//      Or use if (m_bIsClient) as well for dedicated option

void CMultiplayer::update()
{
    
    //If server then send update packets for all network objects
    if (m_bIsServer) {
        
        for (int i=0;i<m_pObjects.size();++i) {
            
            RakNet::BitStream bsUpdateData;
            
            //bsUpdateData.Write(m_pObjects.GetNetworkID());
            
            m_pObjects[i]->generateUpdateBitStream(bsUpdateData);
            
            //Send the update packet to all connected clients
            m_pServer->Send(&bsUpdateData, MEDIUM_PRIORITY, RELIABLE_SEQUENCED, 0, UNASSIGNED_PLAYER_ID, true);
            
        }
        
    }
    
}

void CMultiplayer::disconnect()
{
    
    //TODO: Think about whether m_pServer and m_pClient should be destroyed here or elsewhere
    
    if (m_bIsServer) {
        //m_bIsServer = false;
        m_pServer->Disconnect(300);
        
        //Delete all of the allocated CPlayer instances
        for (PlayerMapIterator itPlayer=m_mPlayers.begin();itPlayer!=m_mPlayers.end();++itPlayer)
        	delete itPlayer->second;
        
        // We're done with the network
        RakNetworkFactory::DestroyRakServerInterface(m_pServer);
    }
    else {
        m_pClient->Disconnect(300);
        
        //Delete the client's CPlayer instance
        delete m_pPlayer;
        
        // We're done with the network
        RakNetworkFactory::DestroyRakClientInterface(m_pClient);
    }
    
}

void CMultiplayer::onClientConnect(PlayerID pidPlayer)
{

	//Create the new player class instance
    CPlayer* pNewPlayer = new CPlayer(pidPlayer);
    //Add it to the map of players
	m_mPlayers[pidPlayer] = pNewPlayer;
	
	//Send the new client a all of the current network objects
    for (int i=0;i<m_pObjects.size();++i) {
        
        //TODO: Replace this "Ship" hack with string type member in gameobject
        //sendCreateNetworkObjectEvent(m_pObjects[i], "Ship");
        
        //NOTE: We are only calling send here, not activate.
		CCreateObjectEvent event(m_pObjects[i], "Ship");
        event.send(pidPlayer);
        
        
    }
    
    //Now create the player's avatar object
    CCreateObjectEvent createNewObject("Ship");
    createNewObject.activate();
    CShip* pShip = (CShip*) createNewObject.getObject();
    //pShip->setVel(Vector(0.0002, 0, 0));
    pShip->setPos(Vector(0, 0, 0));
    
    //Now spawn the player
    //NOTE: May want to change what a spawn event does
    CSpawnEvent spawnEvent(pidPlayer, pShip->getNetIDGenerator().GetNetworkID());
    spawnEvent.activate();
    
    //TODO: Create the new player game Object and let its player class instance know about it
    
    /*
    //Hack to create an object to represent the new client (26-11-07)
    CCreateObjectEvent createNewObject("Ship");
    createNewObject.activate();
    CShip* pShip = (CShip*) createNewObject.getObject();
    //pShip->setVel(Vector(0.0002, 0, 0));
    pShip->setPos(Vector(0, 0, 0));
    */
    //CCreatePlayerEvent serverEvent;
    //serverEvent.activate();
    
}

//TODO: Think about who (can) calls this, and if it should only be the server
CNetworkObject* CMultiplayer::addNetworkObject(String sType)
{
    
    CNetworkObject* pObject = 0;
    
	if (sType == "Ship") {
        
        pObject =  new CShip();
        
    }
    else {
        
        std::cout << "Error - Unknown object type " << sType << std::endl;
        return 0;
        
    }
    
    m_pObjects.push_back(pObject);
    
    return pObject;
    
}

//---------------------------------------------------------

bool CNetIDGenerator::IsNetworkIDAuthority (void) const
{
    
    return CGame::getInstance()->getMultiplayer()->isServer();
    
}

//---------------------------------------------------------

void CNetworkObject::updateFromBitStream(RakNet::BitStream bsData)
{
    
    //Read all the update values from the bitstream into the object
    bsData.Read(m_vPos);
    bsData.Read(m_vVel);
    bsData.Read(m_fAngle);
    bsData.Read(m_fAngVel);
    
}

void CNetworkObject::generateUpdateBitStream(RakNet::BitStream& bsObjectData)
{
    
    //TODO: Look into what NETWORKOBJECT_UPDATE should be
    //Set the type identifier of the packet
    char cTimeStampID = ID_TIMESTAMP;
    bsObjectData.Write(cTimeStampID);
    unsigned long iTimeStamp = CGame::getInstance()->getTimer()->getTime();
    
    bsObjectData.Write(iTimeStamp);
    
    char cTypeID = ID_NETWORKOBJECT_UPDATE;
    bsObjectData.Write(cTypeID);
    
    bsObjectData.Write(m_NetIDGenerator.GetNetworkID());
    
    //Write all the object's updated values to the bitstream
    bsObjectData.Write(m_vPos);
    bsObjectData.Write(m_vVel);
    bsObjectData.Write(m_fAngle);
    bsObjectData.Write(m_fAngVel);
    
}

